/*******************************************************************************
 * Copyright (c) 2003, 2009 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.aseclipse.osgi.classes {
    import org.aseclipse.osgi.Bundle;
    import org.aseclipse.osgi.BundleContext;
    import org.aseclipse.osgi.ServiceEvent;
    import org.aseclipse.osgi.ServiceReference;
    import org.aseclipse.osgi.ServiceRegistration;
    
    import flash.utils.Dictionary;
    


/**
 * A registered service.
 *
 * The framework returns a ServiceRegistration object when a
 * {@link BundleContextImpl#registerService(String, Object, Dictionary) BundleContext.registerService}
 * method is successful. This object is for the private use of
 * the registering bundle and should not be shared with other bundles.
 * <p>The ServiceRegistration object may be used to update the properties
 * for the service or to unregister the service.
 *
 * <p>If the ServiceRegistration is garbage collected the framework may remove
 * the service. This implies that if a
 * bundle wants to keep its service registered, it should keep the
 * ServiceRegistration object referenced.
 * 
 * @ThreadSafe
 */
public class ServiceRegistrationImpl implements ServiceRegistration { // , Comparable
	/** Internal framework object. */
	private var framework:BundleFrameworkImpl;

	private var registry:ServiceRegistry;

	/** context which registered this service. */
	private var context:BundleContext;

	/** bundle which registered this service. */
	private var bundle:Bundle;

	/** service classes for this registration. */
	private var clazzes:Array;

	/** service object for this registration. */
	private var service:Object;

	/** Reference to this registration. */
	/* @GuardedBy("registrationLock") */
	private var reference:ServiceReferenceImpl;

	/** List of contexts using the service. 
	 * List&lt;BundleContextImpl&gt;.
	 * */
	/* @GuardedBy("registrationLock") */
	private var contextsUsing:Array;

	/** properties for this registration. */
	/* @GuardedBy("registrationLock") */
	private var properties:Dictionary;

	/** service id. */
	private var serviceid:Number;

	/** service ranking. */
	/* @GuardedBy("registrationLock") */
	private var serviceranking:int;

	/* internal object to use for synchronization */
	private var registrationLock:Object = new Object();

	/** The registration state */
	/* @GuardedBy("registrationLock") */
	private var state:int = REGISTERED;
	private static const REGISTERED:int = 0x00;
	private static const UNREGISTERING:int = 0x01;
	private static const UNREGISTERED:int = 0x02;

	/**
	 * Construct a ServiceRegistration and register the service
	 * in the framework's service registry.
	 *
	 */
	public function ServiceRegistrationImpl(registry:ServiceRegistry, context:BundleContext, clazzes:Array, service:Object) {
		this.registry = registry;
		this.context = context;
		this.bundle = context.getBundle();
		//this.framework = context.getFramework();
		this.clazzes = clazzes; /* must be set before calling createProperties. */
		this.service = service;
		this.serviceid = registry.getNextServiceId(); /* must be set before calling createProperties. */

		//synchronized (registrationLock) {
			this.contextsUsing = null;
			/* We leak this from the constructor here, but it is ok
			 * because the ServiceReferenceImpl constructor only
			 * stores the value in a final field without
			 * otherwise using it.
			 */
			this.reference = new ServiceReferenceImpl(this);
		//}
	}

	/**
	 * Call after constructing this object to complete the registration.
	 */
	public function register(props:Dictionary):void {
		var ref:ServiceReferenceImpl;
		//synchronized (registry) {
		//	context.checkValid();
			//synchronized (registrationLock) {
				ref = reference; /* used to publish event outside sync */
				this.properties = createProperties(props); /* must be valid after unregister is called. */
			//}
			/* if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
				Debug.println("registerService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			} */
			registry.addServiceRegistration(context, this);
		//}

		/* must not hold the registrations lock when this event is published */
		//registry.publishServiceEvent(new ServiceEvent(ServiceEvent.REGISTERED, ref));
		registry.dispatchEvent(new ServiceEvent(ServiceEvent.REGISTERED, ref));
	}

	/**
	 * Update the properties associated with this service.
	 *
	 * <p>The key "objectClass" cannot be modified by this method. It's
	 * value is set when the service is registered.
	 *
	 * <p>The following steps are followed to modify a service's properties:
	 * <ol>
	 * <li>The service's properties are replaced with the provided properties.
	 * <li>A {@link ServiceEvent} of type {@link ServiceEvent#MODIFIED}
	 * is synchronously sent.
	 * </ol>
	 *
	 * @param props The properties for this service.
	 *        Changes should not be made to this object after calling this method.
	 *        To update the service's properties this method should be called again.
	 * @exception java.lang.IllegalStateException If
	 * this ServiceRegistration has already been unregistered.
	 *
	 * @exception IllegalArgumentException If the <tt>properties</tt>
	 * parameter contains case variants of the same key name.
	 */
	public function setProperties(props:Dictionary):void {
		var ref:ServiceReferenceImpl;
		var previousProperties:Dictionary;
		//synchronized (registrationLock) {
			if (state != REGISTERED) { /* in the process of unregistering */
				// throw new IllegalStateException(Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION);
				throw new Error("Service already unregistered");
			}

			ref = reference; /* used to publish event outside sync */
			previousProperties = this.properties;
			this.properties = createProperties(props);
		//}

		/* must not hold the registrationLock when this event is published */
		//registry.publishServiceEvent(new ModifiedServiceEvent(ref, previousProperties));
	}

	/**
	 * Unregister the service.
	 * Remove a service registration from the framework's service
	 * registry.
	 * All {@link ServiceReferenceImpl} objects for this registration
	 * can no longer be used to interact with the service.
	 *
	 * <p>The following steps are followed to unregister a service:
	 * <ol>
	 * <li>The service is removed from the framework's service
	 * registry so that it may no longer be used.
	 * {@link ServiceReferenceImpl}s for the service may no longer be used
	 * to get a service object for the service.
	 * <li>A {@link ServiceEvent} of type {@link ServiceEvent#UNREGISTERING}
	 * is synchronously sent so that bundles using this service
	 * may release their use of the service.
	 * <li>For each bundle whose use count for this service is greater
	 * than zero:
	 * <ol>
	 * <li>The bundle's use count for this service is set to zero.
	 * <li>If the service was registered with a {@link ServiceFactory},
	 * the {@link ServiceFactory#ungetService ServiceFactory.ungetService} method
	 * is called to release the service object for the bundle.
	 * </ol>
	 * </ol>
	 *
	 * @exception java.lang.IllegalStateException If
	 * this ServiceRegistration has already been unregistered.
	 * @see BundleContextImpl#ungetService
	 */
	public function unregister():void {
		var ref:ServiceReferenceImpl;
		//synchronized (registry) {
			//synchronized (registrationLock) {
				if (state != REGISTERED) { /* in the process of unregisterING */
					throw new Error("Service already unregistered"); // Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION
				}

				/* remove this object from the service registry */
				//if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
				//	Debug.println("unregisterService[" + bundle + "](" + this + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				//}

				registry.removeServiceRegistration(context, this);

				state = UNREGISTERING; /* mark unregisterING */
				ref = reference; /* used to publish event outside sync */
			//}
		//}

		/* must not hold the registrationLock when this event is published */
		registry.dispatchEvent(new ServiceEvent(ServiceEvent.UNREGISTERING, ref));

		var size:int = 0;
		var users:Array = null; // BundleContextImpl[]

		//synchronized (registrationLock) {
			/* we have published the ServiceEvent, now mark the service fully unregistered */
			state = UNREGISTERED;

			if (contextsUsing != null) {
				size = contextsUsing.length;

				if (size > 0) {
					//if (Debug.DEBUG && Debug.DEBUG_SERVICES) {
					//	Debug.println("unregisterService: releasing users"); //$NON-NLS-1$
					//}
					users = contextsUsing; //(BundleContextImpl[]) contextsUsing.toArray(new BundleContextImpl[size]);
				}
			}
		//}

		/* must not hold the registrationLock while releasing services */
		for (var i:int = 0; i < size; i++) {
			releaseService(users[i]);
		}

		//synchronized (registrationLock) {
			contextsUsing = null;

			reference = null; /* mark registration dead */
		//}

		/* The properties field must remain valid after unregister completes. */
	}

	/**
	 * Returns a {@link ServiceReferenceImpl} object for this registration.
	 * The {@link ServiceReferenceImpl} object may be shared with other bundles.
	 *
	 * @exception java.lang.IllegalStateException If
	 * this ServiceRegistration has already been unregistered.
	 * @return A {@link ServiceReferenceImpl} object.
	 */
	public function getReference():ServiceReference {
		return getReferenceImpl();
	}

	protected function getReferenceImpl():ServiceReferenceImpl {
		/* use reference instead of unregistered so that ServiceFactorys, called
		 * by releaseService after the registration is unregistered, can
		 * get the ServiceReference. Note this technically may violate the spec
		 * but makes more sense.
		 */
		//synchronized (registrationLock) {
			if (reference == null) {
				throw new Error("Server already unregistered"); // Msg.SERVICE_ALREADY_UNREGISTERED_EXCEPTION
			}

			return reference;
		//}
	}

	/**
	 * Construct a properties object from the dictionary for this
	 * ServiceRegistration.
	 *
	 * @param p The properties for this service.
	 * @return A Properties object for this ServiceRegistration.
	 */
	/* @GuardedBy("registrationLock") */
	private function createProperties(p:Dictionary):Dictionary  {
		var props:Dictionary = p;

		props["objectClass"] = clazzes;
		props["service.id"] = serviceid;
		//props.setReadOnly();
		//var ranking:Object = props.getProperty(Constants.SERVICE_RANKING);
		//serviceranking = (ranking instanceof Integer) ? ((Integer) ranking).intValue() : 0;

		return props;
	}

	/**
	 * Return the properties object. This is for framework internal use only.
	 * @return The service registration's properties.
	 */
	public function getProperties():Dictionary {
		//synchronized (registrationLock) {
			return properties;
		//}
	}

	/**
	 * Get the value of a service's property.
	 *
	 * <p>This method will continue to return property values after the
	 * service has been unregistered. This is so that references to
	 * unregistered service can be interrogated.
	 * (For example: ServiceReference objects stored in the log.)
	 *
	 * @param key Name of the property.
	 * @return Value of the property or <code>null</code> if there is
	 * no property by that name.
	 */
	public function getProperty(key:String):Object {
		//synchronized (registrationLock) {
			return properties[key];
		//}
	}

	/**
	 * Get the list of key names for the service's properties.
	 *
	 * <p>This method will continue to return the keys after the
	 * service has been unregistered. This is so that references to
	 * unregistered service can be interrogated.
	 * (For example: ServiceReference objects stored in the log.)
	 *
	 * @return The list of property key names.
	 */
	public function getPropertyKeys():Array {
		//synchronized (registrationLock) {
		//	return properties.getPropertyKeys();
		//}
		var keys:Array = new Array();
		for (var item:String in properties) {
		    keys.push(item);
		}
		return keys;
	}

	/**
	 * Return the service id for this service.
	 * @return The service id for this service.
	 */
	protected function getId():Number {
		return serviceid;
	}

	/**
	 * Return the service ranking for this service.
	 * @return The service ranking for this service.
	 */
	//protected function getRanking():int {
		//synchronized (registrationLock) {
	//		return serviceranking;
		//}
	//}

	public function getClasses():Array {
		return clazzes;
	}

	public function getServiceObject():Object {
		return service;
	}

	/**
	 * Return the bundle which registered the service.
	 *
	 * <p>This method will always return <code>null</code> when the
	 * service has been unregistered. This can be used to
	 * determine if the service has been unregistered.
	 *
	 * @return The bundle which registered the service.
	 */
	public function getBundle():Bundle {
		//synchronized (registrationLock) {
			if (reference == null) {
				return null;
			}

			return bundle;
		//}
	}

	/**
	 * Get a service object for the using BundleContext.
	 *
	 * @param user BundleContext using service.
	 * @return Service object
	 */
	public function getService(user:BundleContext):Object {
	    return null;
	}

	/**
	 * Unget a service for the using BundleContext.
	 *
	 * @param user BundleContext using service.
	 * @return <code>false</code> if the context bundle's use count for the service
	 *         is zero or if the service has been unregistered,
	 *         otherwise <code>true</code>.
	 */
	public function ungetService(user:BundleContext):Boolean {
		return true;
	}

	/**
	 * Release the service for the using BundleContext.
	 *
	 * @param user BundleContext using service.
	 */
	protected function releaseService(user:BundleContext):void {
	}

	/**
	 * Return the list of bundle which are using this service.
	 *
	 * @return Array of Bundles using this service.
	 */
	protected function getUsingBundles():Array {
	    return null;
	}

	protected function isAssignableTo(client:Bundle, className:String):Boolean {
		return true;
	}

	/**
	 * Return a String representation of this object.
	 *
	 * @return String representation of this object.
	 */
	public function toString():String {
		var size:int = clazzes.length;
        var sb:String = "";
		sb += '{';

		for (var i:int = 0; i < size; i++) {
			if (i > 0) {
				sb += ", "; //$NON-NLS-1$
			}
			sb += clazzes[i];
		}

		sb += "}="; //$NON-NLS-1$
		for(var item:String in properties) {
		    sb += item + ": " + properties[item];
		}
		return sb;
	}

	/**
	 * Compares this <code>ServiceRegistrationImpl</code> with the specified
	 * <code>ServiceRegistrationImpl</code> for order.
	 * 
	 * <p>
	 * This does a reverse comparison so that the highest item is sorted to the left.
	 * We keep ServiceRegistationImpls in sorted lists such that the highest
	 * ranked service is at element 0 for quick retrieval.
	 * 
	 * @param object The <code>ServiceRegistrationImpl</code> to be compared.
	 * @return Returns a negative integer, zero, or a positive integer if this
	 *         <code>ServiceRegistrationImpl</code> is greater than, equal to, or
	 *         less than the specified <code>ServiceRegistrationImpl</code>.
	 */
	public function compareTo(object:Object):int {
	    return 1;
	}
}
}